"""
filename: QUI_BoatMaker.pyw

A gui for fast audio operations
"""
import subprocess
import sys
import time
import re
import webbrowser
from threading import Thread
from PySide2.QtWidgets import QApplication, QWidget, QFileDialog
from PySide2.QtUiTools import QUiLoader
from PySide2.QtGui import QTextCursor
from autoboat import *
from autoboat.qt_signal import SIGNAL
from pathlib import Path

DIGIT = re.compile(r'[-+]?[\d\.]+')
HHMMSS = re.compile(r'\d{2}:\d{2}:\d{2}')
WEBSITE = 'https://gitee.com/taylorandtony/boat-audio-tools'


def threading_run(func, *args, **kwargs):
    t = Thread(target=func, args=args, kwargs=kwargs, daemon=True)
    t.start()


def get_full_paths(folder: str) -> list:
    """Get all absolute path for sub files inside a folder

    Args:
        folder (str): the folder

    Returns:
        list: _description_
    """
    return [str(Path(folder) / f) for f in os.listdir(folder)]


class MainWindow(QWidget):
    def __init__(self):
        """Main window for this application."""
        super().__init__()
        self.last_path = str(Path.cwd())
        self.latest_output_file = ''
        self.window = QUiLoader().load('main.ui')

        self.window.load1.clicked.connect(self.load1)
        self.window.load2.clicked.connect(self.load2)
        self.window.exec1.clicked.connect(self.exec1)

        self.window.load3.clicked.connect(self.load3)
        self.window.exec2.clicked.connect(self.exec2)

        self.window.load11.clicked.connect(self.load11)
        self.window.load12.clicked.connect(self.load12)
        self.window.load13.clicked.connect(self.load13)
        self.window.exec3.clicked.connect(self.exec3)
        self.window.goto_git.clicked.connect(self.goto_git)

        self.window.to_mp3.clicked.connect(self.to_mp3)
        self.window.to_wav.clicked.connect(self.to_wav)

        self.window.load21.clicked.connect(self.load21)
        self.window.load22.clicked.connect(self.load22)
        self.window.exec20.clicked.connect(self.exec20)

        self.window.video_to_mp3.clicked.connect(self.video_to_mp3)
        self.window.batch_to_mp3.clicked.connect(self.batch_to_mp3)
        self.window.open_latest.clicked.connect(self.open_latest_output_file_path)
        self.window.run_ffmpeg.clicked.connect(self.run_ffmpeg)

        self.window.cut_btn1.clicked.connect(self.cut_btn1)
        self.window.cut_exec.clicked.connect(self.cut_exec)

        SIGNAL.text_print.connect(self.debug_html_with_time)
        SIGNAL.html_print.connect(self.debug_html_with_time)
        SIGNAL.debug_all_done.connect(self.debug_html_with_time)

        self.debug_basic_html('<h3>Welcome to Boat Audio Tools!</h3>')
        self.window.show()

    def debug_basic_html(self, html):
        """ 
        most basic debug function
        print html to debug text, 
        also the callback of debug_all_done.

        Time will not be printed.
        """
        self.window.debug_area.insertHtml(f'{html}<br>')
        self.window.debug_area.moveCursor(QTextCursor.End)

    def debug_html_with_time(self, text):
        """ print regular colorful debug text """
        now = time.strftime("%H:%M:%S", time.localtime())
        self.debug_basic_html(f'<font color="blue">[{now}] </font>{text}')

    def debug_failed(self, text):
        self.debug_html_with_time(f'<font color="red">{text}</font>')

    def all_done(self, extra=''):
        """ threading-safe all done signal """
        # debug_all_done.connect(self.debug_html)
        # equals to print bolded "all done" and extra content.
        SIGNAL.debug_all_done.emit(
                f'<font style="color: green;font-weight:bold">ALL DONE!</font> <span>{extra}</span>')

    def choosePathSubFunc(self):
        """ 选择文件夹 """
        dir_choose = QFileDialog.getExistingDirectory(self.window, "选取文件夹",
                                                      self.last_path)  # 起始路径
        if dir_choose == "":
            print("\n取消选择")
            return
        print("\n你选择的文件夹为:")
        print(dir_choose)
        self.debug_html_with_time(f'选择：{dir_choose}')
        if dir_choose:
            self.last_path = str(dir_choose)
        return dir_choose

    def chooseFileSubFunc(self):
        """ 选择一个文件 """
        fileName_choose, filetype = QFileDialog.getOpenFileName(
                self.window,
                "选取文件",
                self.last_path,  # 起始路径
                "All Files (*)")  # 设置文件扩展名过滤,用双分号间隔
        if fileName_choose == "":
            print("\n取消选择")
            return
        print("\n你选择的文件为:")
        print(fileName_choose)
        print("文件筛选器类型: ", filetype)
        self.debug_html_with_time(f'选择：{fileName_choose}')
        if fileName_choose:
            self.last_path = str(Path(fileName_choose).parent)
        return fileName_choose

    def open_latest_output_file_path(self):
        """ Open the latest output file path """
        if not self.latest_output_file:
            self.debug_failed('没有新生成的文件')
            return
        p = Path(self.latest_output_file)
        if p.exists():
            if p.is_dir():
                os.startfile(str(p))
            else:
                os.startfile(str(p.parent))

    def load1(self):
        """ Callback button of load1: add bgm to audio """
        if file := self.chooseFileSubFunc():
            self.window.file1.setText(file)

    def load2(self):
        """ Callback button of load2: add bgm to audio """
        if file := self.chooseFileSubFunc():
            self.window.file2.setText(file)

    def exec1(self):
        """ Callback button of exec1: add bgm to audio """
        top = self.window.file1.text()
        bgm = self.window.file2.text()

        dbfs1 = self.window.dbfs1.text()
        dbfs2 = self.window.dbfs2.text()

        if DIGIT.match(dbfs1) and DIGIT.match(dbfs2):
            self.debug_html_with_time('Use custom dBFS volume')
            vol1, vol2 = float(dbfs1), float(dbfs2)
            if vol1 > 0.0 and vol2 > 0.0:
                self.debug_html_with_time('dBFS value must < 0')
                return
        else:
            self.debug_html_with_time('Use default dBFS volume')
            vol1, vol2 = -12.0, -22.0

        if not (top and bgm):
            self.debug_failed('请先选择文件')
            return

        def callback():
            top_audio = robust_load_audio(top)
            bgm_audio = robust_load_audio(bgm)
            audio = make_audio_with_bgm(top_audio, bgm_audio, vol1, vol2)
            save_to = Path(top).parent / (Path(top).stem + '_with_bgm.wav')
            save_to_file(audio, save_to)
            self.all_done(f'saved to <strong>{save_to}</strong>')
            self.latest_output_file = save_to

        threading_run(callback)

    def load3(self):
        """ Callback button of load3: concat all audios """
        if file := self.choosePathSubFunc():
            self.window.file3.setText(file)

    def exec2(self):
        """ Callback button of exec2: concat all audios """
        folder = self.window.file3.text()
        # this is a folder
        if not folder:
            self.debug_failed('需要先选择文件夹')
            return
        save_to = Path(folder) / 'merge.wav'

        all_files = get_full_paths(folder)
        concat_audio_files(all_files, save_to)
        self.all_done(f'saved to <strong>{save_to}</strong>')
        self.latest_output_file = save_to

    def load11(self):
        """ Callback button of load11 """
        if file := self.choosePathSubFunc():
            self.window.file11.setText(file)

    def load12(self):
        """ Callback button of load12 """
        if file := self.chooseFileSubFunc():
            self.window.file12.setText(file)

    def load13(self):
        """ Callback button of load13 """
        if file := self.chooseFileSubFunc():
            self.window.file13.setText(file)

    def exec3(self):
        """ Callback button of exec3 """
        audio_folder = self.window.file11.text()
        bgm = self.window.file12.text()
        end = self.window.file13.text()

        if not (audio_folder and bgm and end):
            self.debug_failed('请完整填写文件路径')
            return

        save_to = Path(audio_folder) / 'classic_boat.wav'

        def callback():
            classic_compose_boat(
                    get_full_paths(audio_folder),
                    bgm,
                    end,
                    save_to)
            self.all_done(f'Classic boat audio done at <strong>{save_to}</strong>')
            self.latest_output_file = save_to

        threading_run(callback)

    def run_ffmpeg(self):
        """ Callback button of run_ffmpeg """
        ffmpeg_script = self.window.src_area.toPlainText()
        lines = ffmpeg_script.split('\n')
        if not lines:
            self.debug_failed('请输入ffmpeg命令')
            return
        for line in lines:
            if not line:
                continue
            if line.startswith('#'):
                continue
            self.debug_html_with_time(f'Running: {line}')
            proc = subprocess.run(line, shell=True)
            self.debug_html_with_time(f'Command finished with code <strong>{proc.returncode}</strong>')

    def goto_git(self):
        """ Callback button of goto_git """
        webbrowser.open(WEBSITE)

    def to_wav(self):
        if file := self.chooseFileSubFunc():
            if file.endswith(".wav"):
                self.debug_failed(f'{file} is already wav')
                return

        def callback():
            save_to = Path(file).with_suffix('.wav')
            cvt_any_to_wav(file, save_to)
            self.all_done(f'save to <strong>{save_to}</strong>')
            self.latest_output_file = save_to

        threading_run(callback)

    def to_mp3(self):
        if file := self.chooseFileSubFunc():
            if file.endswith(".mp3"):
                self.debug_failed(f'{file} is already mp3')
                return

        def callback():
            save_to = Path(file).with_suffix('.mp3')
            cvt_any_to_mp3(file, save_to)
            self.all_done(f'save to <strong>{save_to}</strong>')
            self.latest_output_file = save_to

        threading_run(callback)

    def load21(self):
        """ Load file for batch add bgm """
        if file := self.choosePathSubFunc():
            self.window.file21.setText(file)

    def load22(self):
        """ Load file for batch add bgm """
        if file := self.choosePathSubFunc():
            self.window.file22.setText(file)

    def exec20(self):
        """ Batch add bgm """
        fo1 = self.window.file21.text()
        fo2 = self.window.file22.text()
        if not (fo1 and fo2):
            self.debug_failed("请先选择文件夹")
            return
        audios = get_full_paths(fo1)
        bgms = get_full_paths(fo2)

        def callback():
            idx = 0
            for a, b in zip(audios, bgms):
                idx += 1
                save_to = f'mixed_{idx}.wav'
                make_audio_with_bgm_and_export(
                        a, b, Path(fo1).parent / save_to)
            self.all_done(f'<strong>{idx}</strong> audio processed.')
            self.latest_output_file = Path(fo1).parent / save_to

        threading_run(callback)

    def batch_to_mp3(self):
        """Batch convert all wav file to mp3
        """
        file = self.choosePathSubFunc()

        if not file:
            return

        def callback():
            nonlocal file
            p = Path(file)
            cnt = 0
            for wav in p.glob('*.wav'):
                cnt += 1
                file = p / wav.name
                save = file.with_suffix('.mp3')
                cvt_any_to_mp3(file, save)
                self.all_done(f'total <strong>{cnt}</strong> processed.')
                self.latest_output_file = file

        threading_run(callback)

    def video_to_mp3(self):
        file = self.chooseFileSubFunc()

        if not file:
            self.debug_failed('请先选择文件')
            return
        # ffmpeg -i input.mp4 -q:a 0 -map a output.mp3
        output_file = Path(file).resolve().with_suffix('.mp3')

        def callback():
            nonlocal file
            os.popen(
                    f'ffmpeg -i "{file}" -q:a 0 -map a "{output_file}"').read()
            self.all_done(f'<strong>"{file}"</strong> converted to <strong>"{output_file}"</strong>')
            self.latest_output_file = output_file

        threading_run(callback)

    def cut_btn1(self):
        """ choose a file to be cut """
        if file := self.chooseFileSubFunc():
            self.window.cut_line1.setText(file)

    def cut_exec(self):
        """ execute the cut process """
        begin = self.window.cut_line2.text()
        end = self.window.cut_line3.text()
        if not HHMMSS.match(begin):
            self.debug_failed('请填写正确的开始时间')
            return
        if not HHMMSS.match(end):
            self.debug_failed('请填写正确的结束时间')
            return
        self.debug_html_with_time(f'Cutting from {begin} to {end}...')
        ori_file = self.window.cut_line1.text()
        ori_path = Path(ori_file)
        if not ori_path.exists():
            self.debug_failed(f'{ori_file} not exists')
            return

        ext = ori_path.suffix
        new_file = ori_path.with_suffix(f'.cut{ext}')
        new_path = new_file.resolve()
        cmd = f'ffmpeg -i "{ori_file}" -ss {begin} -to {end} -c copy "{new_path}"'
        proc = subprocess.run(cmd, shell=True)
        ret_code = proc.returncode
        self.debug_html_with_time(f'Command finished with code <strong>{ret_code}</strong>')
        self.debug_html_with_time(f'Save to <strong>{new_path}</strong>')


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())
